Skip to content

feat: Made upload sizes configurable#458

Merged
turegjorup merged 12 commits into
release/3.0.0from
feature/configurable-upload-size
May 26, 2026
Merged

feat: Made upload sizes configurable#458
turegjorup merged 12 commits into
release/3.0.0from
feature/configurable-upload-size

Conversation

@tuj

@tuj tuj commented May 19, 2026

Copy link
Copy Markdown
Contributor

Link to issue

#392

Link to ticket

https://leantime.itkdev.dk/#/tickets/showTicket/7259

Description

Made upload sizes configurable.

Checklist

  • My code is covered by test cases.
  • My code passes our test (all our tests).
  • My code passes our static analysis suite.
  • My code passes our continuous integration process.

@tuj tuj changed the title Made upload sizes configurable feat: Made upload sizes configurable May 19, 2026
@tuj tuj self-assigned this May 19, 2026
@tuj tuj added bug Something isn't working enhancement New feature or request version 3 Issues related to the release of version 3 documentation Improvements or additions to documentation labels May 19, 2026
@tuj
tuj requested a review from turegjorup May 20, 2026 10:18
…traint

Custom constraint reads MEDIA_MAX_UPLOAD_SIZE_MB from DI so the limit stays
configurable while the invariants live on the entity, not in the controller.
Without this every consumer mount re-fired a failing /config/admin request.
…SizeMb

Initial state is null and the dropzone is hidden behind a loading placeholder
until config resolves; a non-positive integer falls back to 200 instead of
being silently absorbed.
…igurable-upload-size

# Conflicts:
#	CHANGELOG.md
@turegjorup

turegjorup commented May 26, 2026

Copy link
Copy Markdown
Contributor

Response to review findings

Updated to reflect commits landed on this branch.

Addressed

Finding Commit
MediaController.php:41 (now MediaFileValidator.php) — '0Mi' rejects every byte when env coerces to 0 6d0326c3__construct throws \InvalidArgumentException naming MEDIA_MAX_UPLOAD_SIZE_MB on any non-positive value; dataprovider test covers 0 and -1
.env:111 vs README — cache:clear contradiction a016403e.env note now states the value is read per-request; also documents the MiB unit and nginx/php-fpm alignment requirement
file-selector.jsx:39 — falsy-zero guard ignores server value 0 bd25feac — replaced with Number.isInteger(value) && value > 0 ? value : 200
file-selector.jsx:34 — dropzone renders permissive 200 MB default before config loads bd25feac — initial state null; dropzone hidden behind file-selector.loading placeholder until config resolves; new vitest case covers the transition
admin-config-loader.js:28 — fallback resolved but not cached, every consumer mount re-fired the failing fetch b5f594c9 — defaults hoisted to module-level DEFAULT_CONFIG; catch path now assigns configData = DEFAULT_CONFIG
admin-config-loader.js:10 — latent stale activePromise on the cache-hit branch b5f594c9 — rewrite removes the wrapping new Promise(...); activePromise only set during fetch, cleared in .finally
services.yaml:72AdminConfigController arguments block shadowed the global bind for 6/7 params a8515445 — removed the global $mediaMaxUploadSizeMb bind entirely; explicit per-service wiring for AdminConfigController and the new MediaFileValidator
MediaController.php:38 — validation order surfaced size/mime error before missing-title a8515445 — title/description/license are read before the entity validator runs, so MediaException('Missing request parameter: title') fires first
MediaTest.php:166 — stale Media::loadValidatorMetadata comment a8515445 — updated to reference the new #[MediaFile] attribute on Media::$file

Structural change (a8515445)

The size/mime constraint moved off the controller and back onto the entity via a custom App\Validator\MediaFile constraint + validator (the API Platform MinimalProperties pattern). The controller now calls $this->validator->validate($media) against the assembled entity; the validator reads MEDIA_MAX_UPLOAD_SIZE_MB via DI. Restores the entity as the single source of truth for upload invariants and removes the inline new Assert\File(...) from the controller. Also dropped the redundant env(MEDIA_MAX_UPLOAD_SIZE_MB): '200' parameter fallback in services.yaml.env is the sole default, consistent with how other env-driven params are handled.

Reclassified after investigation

  • MediaController.php:30 — no handling of UPLOAD_ERR_INI_SIZE / UPLOAD_ERR_FORM_SIZE. Original finding was overstated. Symfony's Assert\File already branches on UploadedFile::getError() and emits a specific violation for every PHP upload error code (vendor/symfony/validator/Constraints/FileValidator.php:54-116); our MediaFileValidator delegates to it, so the upload_max_filesize case surfaces as a 422 with a clear "file is too large" message for free. Only post_max_size remains uncovered — PHP discards the body before Symfony's kernel runs, so there's no UploadedFile for any validator to inspect. With the PR's defaults (PHP-FPM ≥ nginx ≥ app at 200 MiB), nginx is the strictest layer and rejects with 413 before PHP runs, making this hard to hit in practice. Leaving the misleading "file is required" 400 for the narrow post_max_size case as a known gap.

Downgraded

  • docker-compose.yml:72NGINX_MAX_BODY_SIZE: 5M — confirmed dev-only stack. Production runs the image built from infrastructure/nginx/Dockerfile whose NGINX_MAX_BODY_SIZE=200m default is already aligned. Local dev value is a dev-UX inconvenience for testing the new feature locally, not a release blocker.

Not addressed (deferred / out of scope)

Listed for tracking. Happy to pull any of these into this PR if you'd like:

  • MediaTest.php:169 — skip-gate reads $_ENV directly. Silent skip if dotenv isn't populated. Should read via container/parameter instead.
  • MediaController200Mi (binary) vs historical 200000k (decimal) ≈ 4.86% looser default. Worth either a CHANGELOG note or switching to 200M.
  • file-dropzone.jsx:29 — error message mixes decimal MB display (/1000000) with binary MiB threshold (*1024*1024). Near boundary yields self-contradictory "X MB > X MB".
  • infrastructure/nginx/Dockerfile:31 — nginx body size hard-coded 200m, not derived from MEDIA_MAX_UPLOAD_SIZE_MB. Raising the app limit silently misaligns prod.

Constructor guard turns a 0 (or empty/negative env) misconfig into a clear
500 naming the env var, instead of a 422 "file too large (0 bytes)" that
blames the user's upload.
The value is read per-request via %env(int:...)%; no validator cache to clear.
Also documented the MiB unit and the alignment requirement with nginx/php-fpm.
@turegjorup
turegjorup merged commit 22b7baa into release/3.0.0 May 26, 2026
@turegjorup
turegjorup deleted the feature/configurable-upload-size branch May 26, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request version 3 Issues related to the release of version 3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants